package net.oschina.simpleplus.gen;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.protobuf.ByteString;

import net.oschina.simpleplus.entity.Protobuf;

public class JavaBeanEntity {

	private String projectDir;
	private String pkgName;
	private String javaBeanName;
	private Class<?> protoJavaCls;//proto message name;
	
	private List<ProtoField> protoFields;//字段
	private Set<String> needImportPks;//字段需要导入的包名
	
	private Map<Class<?>, String> clsMap;
	
	public JavaBeanEntity(String projectDir, String pkgName, String javaBeanName, Class<?> protoJavaCls) {
		this(projectDir, pkgName, javaBeanName, protoJavaCls, null);
	}
	
	public JavaBeanEntity(String projectDir, String pkgName, String javaBeanName, Class<?> protoJavaCls,
			Map<Class<?>, String> clsMap) {
		
		this.projectDir = projectDir;
		this.pkgName = pkgName;
		this.javaBeanName = javaBeanName;
		this.protoJavaCls = protoJavaCls;
		
		if(StringUtils.isEmpty(this.projectDir)){
			this.projectDir = System.getProperty("user.dir");
		}
		if(this.projectDir.endsWith(File.separator)){
			this.projectDir.substring(0, this.projectDir.lastIndexOf(File.separator));
		}
		
		this.clsMap = clsMap;
	}

	public void parse(){
		protoFields = new ArrayList<ProtoField>();
		needImportPks = new HashSet<String>();
		
		Field[] fs = protoJavaCls.getDeclaredFields();
		for(Field f : fs){
			String name = f.getName();
			if(!name.endsWith("_") || "bitField0_".equals(name)){
				continue;
			}
			name = name.substring(0, name.lastIndexOf('_'));
			if(ByteString.class.equals(f.getType())){
				protoFields.add(new ProtoField(name, "byte[]"));
				needImportPks.add("import com.google.protobuf.ByteString;");
			}else if(Object.class.equals(f.getType())){
				try {
					String canonicalName = protoJavaCls.getMethod("get"+StringUtils.capitalize(name), null).getReturnType().getCanonicalName();
					if("java.lang.String".equals(canonicalName)){
						protoFields.add(new ProtoField(name, canonicalName));
					}
				} catch (NoSuchMethodException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (SecurityException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}else if(List.class.isAssignableFrom(f.getType())){
				try {
					Class<?> returnType = protoJavaCls.getMethod("get"+StringUtils.capitalize(name), int.class).getReturnType();
					String canonicalName = ClassUtils.primitiveToWrapper(returnType).getCanonicalName();
					if("com.google.protobuf.ByteString".equals(canonicalName)){
						protoFields.add(new ProtoFieldList(name, "byte[]"));
						needImportPks.add("import com.google.protobuf.ByteString;");
						needImportPks.add("import net.oschina.simpleplus.util.ConvertUtil;");
					}else if(ClassUtils.isPrimitiveOrWrapper(returnType)){
						protoFields.add(new ProtoFieldList(name, canonicalName));
					}else{
						protoFields.add(new ProtoFieldBeanList(name, clsMap.get(returnType), returnType.getSimpleName()));
						needImportPks.add("import net.oschina.simpleplus.util.Convertor;");
						needImportPks.add("import net.oschina.simpleplus.util.ConvertUtil;");
						needImportPks.add("import " + returnType.getName().replaceAll("\\$", "\\.") + ";" );
						needImportPks.add("import " + clsMap.get(returnType) + ";");
					}
				} catch (NoSuchMethodException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (SecurityException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				needImportPks.add("import java.util.List;");
				needImportPks.add("import java.util.ArrayList;");
			}else if(ClassUtils.isPrimitiveOrWrapper(f.getType())){
				protoFields.add(new ProtoField(name, f.getType().getName()));
			}else if(clsMap != null){
				String valueName = clsMap.get(f.getType());
				protoFields.add(new ProtoFieldBean(name, valueName));
				if(!pkgName.equals(valueName.substring(0, valueName.lastIndexOf(".")))){
					needImportPks.add("import " + valueName+";");
				}
				
			}
		}
	}

	public void generate(){
		StringBuffer buffer = new StringBuffer();
		if(pkgName != null){
			appendInLine(buffer.append("package ").append(pkgName).append(";"));
		}
		if(!pkgName.equals(Protobuf.class.getPackage().getName()))
			appendInLine(buffer.append("import ").append(Protobuf.class.getName()).append(";"));
		appendInLine(buffer.append("import com.google.protobuf.InvalidProtocolBufferException;"));
		appendInLine(buffer.append("import ").append(protoJavaCls.getName().replaceAll("\\$", "\\.")).append(";"));
		if(!needImportPks.isEmpty()){
			for(String str : needImportPks){
				appendInLine(buffer.append(str));
			}
		}
		appendInLine(buffer);
		appendInLine(buffer);
		appendInLine(buffer.append("public class ").append(javaBeanName).append(" implements Protobuf<").append(protoJavaCls.getSimpleName())
				.append("> {"));
		
		appendInLine(buffer);
		appendInLine(buffer.append("\tpublic ").append(javaBeanName).append("(){"));
		if(protoFields != null && !protoFields.isEmpty()){
			for(ProtoField protoField : protoFields){
				buffer = protoField.appendEmptyConstructorMethod(buffer);
			}
		}
		appendInLine(buffer.append("\n\t}"));
		
		if(protoFields != null && !protoFields.isEmpty()){
			for(ProtoField protoField : protoFields){
				buffer = protoField.appendField(buffer);
			}
			appendInLine(buffer);
			for(ProtoField protoField : protoFields){
				buffer = appendInLine(protoField.appendGetterMethod(buffer));
				
				buffer = appendInLine(protoField.appendSetterMethod(buffer));
			}
		}
		//build();
		appendInLine(buffer);
		appendInLine(buffer);
		appendInLine(buffer.append("\t@Override"));
		appendInLine(buffer.append("\tpublic ").append(protoJavaCls.getSimpleName()).append(" build(){"));
		//JavaBeanMessage.Builder builder = JavaBeanMessage.newBuilder();
		buffer.append("\t").append(protoJavaCls.getSimpleName()).append(".Builder builder= ").append(protoJavaCls.getSimpleName()).append(".newBuilder();");
		if(protoFields != null && !protoFields.isEmpty()){
			for(ProtoField protoField : protoFields){
				buffer = protoField.appendBuildMethod(buffer);
			}
			appendInLine(buffer);
		}
		appendInLine(buffer.append("\t\treturn builder.build();"));
		appendInLine(buffer.append("\t}"));//end

		
		//toByteArray
		appendInLine(buffer);
		appendInLine(buffer);
		
		appendInLine(buffer.append("\t@Override"));
		appendInLine(buffer.append("\tpublic byte[] toByteArray() {\n\t\treturn build().toByteArray();\n\t}"));
		
		//parseFrom
		appendInLine(buffer);
		appendInLine(buffer);
		
		appendInLine(buffer.append("\t@Override"));
		appendInLine(buffer.append("\tpublic void parseFrom(byte[] bytes) throws InvalidProtocolBufferException {\n\t\tcopyFrom(")
				.append(protoJavaCls.getSimpleName()).append(".parseFrom(bytes));\n\t}"));
		
		
		//copyFrom
		appendInLine(buffer);
		appendInLine(buffer);
		
		appendInLine(buffer.append("\t@Override"));
		buffer.append("\tpublic void copyFrom(").append(protoJavaCls.getSimpleName()).append(" t) {");
		if(protoFields != null && !protoFields.isEmpty()){
			for(ProtoField protoField : protoFields){
				buffer = protoField.appendCopyFromMethod(buffer);
			}
		}
		appendInLine(buffer.append("\n\t}"));//end
		buffer.append("}");
		
		File file = new File(projectDir + covert("src.main.java." + pkgName ) + "/"+javaBeanName +".java");
		try {
			if(!file.getParentFile().exists()){
				file.getParentFile().mkdirs();
			}
			FileUtils.write(file, buffer, "UTF-8");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		clsMap.put(protoJavaCls, pkgName + "." + javaBeanName + ".java");
	}
	
	private static StringBuffer appendInLine(StringBuffer buffer) {
		return buffer.append("\n");
	}
	
	private static String covert(String pksName){
		StringBuffer buffer = new StringBuffer();
		for(String m : pksName.split("\\.")){
			buffer.append(File.separator).append(m);
		}
		return buffer.toString();
	}
	
}
